├── .editorconfig ├── .github └── workflows │ └── test.yml ├── .gitignore ├── PREREQUISITES.md ├── README.md ├── TROUBLESHOOTING.md ├── behat.yml ├── bin └── install-package-tests.sh ├── command.php ├── composer.json ├── features ├── valet-destroy.feature ├── valet-new-project-bedrock.feature └── valet-new.feature ├── src ├── Composer.php ├── Config.php ├── Facade.php ├── Installer │ ├── BedrockInstaller.php │ ├── ComposerRequireSqliteIntegration.php │ ├── InstallerInterface.php │ └── WordPressInstaller.php ├── Process │ ├── FakeValet.php │ ├── ShellCommand.php │ ├── SystemComposer.php │ ├── SystemValet.php │ ├── SystemWp.php │ ├── ValetInterface.php │ └── WpInterface.php ├── Props.php ├── Valet.php ├── ValetCommand.php ├── ValetConfig.php └── WP.php ├── tests └── Context │ └── FeatureContext.php └── utils └── behat-tags.php /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | charset = utf-8 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | indent_style = space 11 | indent_size = 4 12 | 13 | [*.{jshintrc,json,yml,feature}] 14 | indent_size = 2 15 | 16 | [{*.txt,wp-config-sample.php}] 17 | end_of_line = crlf 18 | 19 | [composer.json] 20 | indent_size = 4 21 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - master 7 | paths-ignore: 8 | - '**.md' 9 | workflow_dispatch: 10 | workflow_call: 11 | 12 | concurrency: 13 | group: ${{ github.workflow }}-${{ github.ref }} 14 | cancel-in-progress: true 15 | 16 | jobs: 17 | lint: 18 | strategy: 19 | matrix: 20 | php-version: ['7.4'] 21 | runs-on: ubuntu-latest 22 | steps: 23 | - uses: actions/checkout@v4 24 | 25 | - name: Setup PHP 26 | uses: shivammathur/setup-php@v2 27 | with: 28 | php-version: ${{ matrix.php-version }} 29 | tools: composer 30 | 31 | - name: Validate composer.json 32 | run: composer validate --strict 33 | 34 | behat: 35 | needs: lint 36 | strategy: 37 | matrix: 38 | php-version: ['7.4', '8.2'] 39 | runs-on: ubuntu-latest 40 | steps: 41 | - uses: actions/checkout@v4 42 | 43 | - name: Setup PHP 44 | uses: shivammathur/setup-php@v2 45 | with: 46 | php-version: ${{ matrix.php-version }} 47 | tools: composer 48 | 49 | - run: composer install 50 | 51 | - run: | 52 | sudo systemctl start mysql.service 53 | mysql -e "CREATE DATABASE ${WP_CLI_TEST_DBNAME};" -uroot -proot 54 | mysql -e "CREATE USER '${WP_CLI_TEST_DBUSER}'@'localhost' IDENTIFIED BY '${WP_CLI_TEST_DBPASS}';" -uroot -proot 55 | mysql -e "GRANT ALL PRIVILEGES ON ${WP_CLI_TEST_DBNAME}.* TO '${WP_CLI_TEST_DBUSER}'@'localhost';" -uroot -proot 56 | mysql -e "ALTER USER 'root'@'localhost' IDENTIFIED BY ''; FLUSH PRIVILEGES;" -uroot -proot 57 | composer run behat 58 | env: 59 | MYSQL_ALLOW_EMPTY_PASSWORD: yes 60 | MYSQL_DATABASE: wp_cli_test 61 | MYSQL_USER: wp_cli_test 62 | MYSQL_PASSWORD: password1 63 | MYSQL_HOST: 127.0.0.1 64 | WP_CLI_TEST_DBUSER: wp_cli_test 65 | WP_CLI_TEST_DBPASS: password1 66 | WP_CLI_TEST_DBNAME: wp_cli_test 67 | WP_CLI_TEST_DBHOST: 127.0.0.1:3306 68 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | composer.lock 3 | node_modules/ 4 | vendor/ 5 | .idea 6 | /wordpress 7 | /wp-content 8 | -------------------------------------------------------------------------------- /PREREQUISITES.md: -------------------------------------------------------------------------------- 1 | This command leverages [Laravel Valet](https://laravel.com/docs/valet) -- an open source development environment for Mac + \*nix minimalists. 2 | 3 | It runs various commands lightning fast, allowing you to spin up a site in your browser immediately after creating it, without any other configuration, all from a single command. 4 | 5 | You should also understand how Valet works, especially the portion on [Serving Sites](https://laravel.com/docs/5.2/valet#serving-sites). 6 | 7 | #### Environment Setup 8 | 0) MacOS users should install [Homebrew](https://brew.sh/) first. 9 | 10 | 1) Follow the [Valet installation instructions](https://laravel.com/docs/valet#installation) on the Laravel documentation to get started. 11 | 12 | > _Note: Linux users should use [Valet-linux](https://github.com/cpriego/valet-linux) instead, a fork of the original project that shares most of the same `valet` commands powering this `wp-cli` plugin._ 13 | 14 | 2) Confirm your `wp-cli` environment works and meets the minimum version specified below by running `wp cli info` and proceed if the output looks something like: 15 | ``` 16 | PHP binary: /usr/bin/php7.0 17 | PHP version: 7.0.22-0ubuntu0.16.04.1 18 | php.ini used: /etc/php/7.0/cli/php.ini 19 | WP-CLI root dir: phar://wp-cli.phar 20 | WP-CLI vendor dir: phar://wp-cli.phar/vendor 21 | WP_CLI phar path: /home/user/wp-cli-valet-command 22 | WP-CLI packages dir: /home/user/.wp-cli/packages/ 23 | WP-CLI global config: /home/user/.wp-cli/config.yml 24 | WP-CLI project config: 25 | WP-CLI version: 1.4.1 26 | ``` 27 | 28 | Update, if needed, to the latest stable release with `wp cli update`. 29 | 30 | #### Loading the wp-cli-valet-command package 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | aaemnnosttv/wp-cli-valet-command 2 | ================================ 3 | 4 | White-glove services for turn-key installs in seconds. 5 | 6 | [![Packagist](https://img.shields.io/packagist/v/aaemnnosttv/wp-cli-valet-command.svg)](https://packagist.org/packages/aaemnnosttv/wp-cli-valet-command) 7 | 8 | Quick links: [Using](#using) | [Installing](#installing) | [Troubleshooting](#troubleshooting) | [Support](#support) | [Contributing](#contributing) 9 | 10 | ## Using 11 | 12 | This package implements the following commands: 13 | 14 | ### wp valet new 15 | 16 | Create a new WordPress install -- fast 17 | 18 | ~~~ 19 | wp valet new [--project=] [--in=] [--version=] [--locale=] [--db=] [--dbname=] [--dbuser=] [--dbpass=] [--dbhost=] [--dbprefix=] [--admin_user=] [--admin_password=] [--admin_email=] [--unsecure] [--portable] 20 | ~~~ 21 | 22 | This command will spin up a new WordPress installation -- complete with database and https 23 | _ready-to-use in your browser_ faster than you can put your pants on. 24 | 25 | **NB** If you have not used `valet park` for the directory or parent directory you are 26 | running the installation in you need to do a `valet link` to make sure the site will run 27 | without running into 404s. 28 | 29 | **OPTIONS** 30 | 31 | 32 | Site domain name without TLD. This will become the directory name of the project root. 33 | Eg: To create an install for example.dev, `wp valet new example` 34 | 35 | [--project=] 36 | The WordPress project to install. Choose from any project supported by Laravel Valet. 37 | --- 38 | default: wp 39 | options: 40 | - wp 41 | - bedrock 42 | --- 43 | 44 | [--in=] 45 | Specify the path to the parent directory to create the install in. 46 | Defaults to the current working directory. 47 | 48 | [--version=] 49 | WordPress version to install. 50 | --- 51 | default: latest 52 | --- 53 | 54 | [--locale=] 55 | Select which language you want to install. 56 | 57 | [--db=] 58 | Database driver to provision the site with. 59 | --- 60 | default: mysql 61 | options: 62 | - mysql 63 | - sqlite 64 | --- 65 | 66 | [--dbname=] 67 | Database name (MySQL only). 68 | Defaults to 'wp_'. 69 | 70 | [--dbuser=] 71 | Database User (MySQL only). 72 | --- 73 | default: root 74 | --- 75 | 76 | [--dbpass=] 77 | Set the database user password (MySQL only). 78 | --- 79 | Default: '' 80 | --- 81 | 82 | [--dbhost=] 83 | Set the database host. 84 | --- 85 | default: localhost 86 | --- 87 | 88 | [--dbprefix=] 89 | Set the database table prefix. 90 | --- 91 | default: 'wp_' 92 | --- 93 | 94 | [--admin_user=] 95 | The username to create for the WordPress admin user. 96 | --- 97 | default: admin 98 | --- 99 | 100 | [--admin_password=] 101 | The password to create for the WordPress admin user. 102 | --- 103 | default: admin 104 | --- 105 | 106 | [--admin_email=] 107 | The email to use for the WordPress admin user. 108 | 109 | [--unsecure] 110 | Provision the site for http rather than https. 111 | 112 | [--portable] 113 | Provision the site to be portable. Implies --unsecure and --db=sqlite. 114 | 115 | 116 | 117 | ### wp valet destroy 118 | 119 | Completely remove an installation. 120 | 121 | ~~~ 122 | wp valet destroy [--yes] 123 | ~~~ 124 | 125 | This will drop the database, and delete all of the files as well as 126 | remove any self-signed TLS certificate that was generated for serving 127 | this install over https. 128 | 129 | **OPTIONS** 130 | 131 | 132 | Site domain name without TLD. It should match the directory name of the project root. 133 | 134 | [--yes] 135 | Pre-approve the confirmation to delete all files and drop the database. 136 | 137 | ## Installing 138 | 139 | This command leverages [Laravel Valet](https://laravel.com/docs/valet) -- an open source development environment for Mac + \*nix minimalists. 140 | 141 | It runs various commands lightning fast, allowing you to spin up a site in your browser immediately after creating it, without any other configuration, all from a single command. 142 | 143 | You should also understand how Valet works, especially the portion on [Serving Sites](https://laravel.com/docs/5.2/valet#serving-sites). 144 | 145 | #### Environment Setup 146 | 0) MacOS users should install [Homebrew](https://brew.sh/) first. 147 | 148 | 1) Follow the [Valet installation instructions](https://laravel.com/docs/valet#installation) on the Laravel documentation to get started. 149 | 150 | > _Note: Linux users should use [Valet-linux](https://github.com/cpriego/valet-linux) instead, a fork of the original project that shares most of the same `valet` commands powering this `wp-cli` plugin._ 151 | 152 | 2) Confirm your `wp-cli` environment works and meets the minimum version specified below by running `wp cli info` and proceed if the output looks something like: 153 | ``` 154 | PHP binary: /usr/bin/php7.0 155 | PHP version: 7.0.22-0ubuntu0.16.04.1 156 | php.ini used: /etc/php/7.0/cli/php.ini 157 | WP-CLI root dir: phar://wp-cli.phar 158 | WP-CLI vendor dir: phar://wp-cli.phar/vendor 159 | WP_CLI phar path: /home/user/wp-cli-valet-command 160 | WP-CLI packages dir: /home/user/.wp-cli/packages/ 161 | WP-CLI global config: /home/user/.wp-cli/config.yml 162 | WP-CLI project config: 163 | WP-CLI version: 1.4.1 164 | ``` 165 | 166 | Update, if needed, to the latest stable release with `wp cli update`. 167 | 168 | #### Loading the wp-cli-valet-command package 169 | 170 | Installing this package requires WP-CLI v1 || v2 or greater. Update to the latest stable release with `wp cli update`. 171 | 172 | Once you've done so, you can install the latest stable version of this package with: 173 | 174 | ```bash 175 | wp package install aaemnnosttv/wp-cli-valet-command:@stable 176 | ``` 177 | 178 | To install the latest development version of this package, use the following command instead: 179 | 180 | ```bash 181 | wp package install aaemnnosttv/wp-cli-valet-command:dev-master 182 | ``` 183 | 184 | ## Troubleshooting 185 | 186 | `Error: ERROR 1045 (28000): Access denied for user 'root'@'localhost'` 187 | The installer halts at the database creation stage because it doesn't have a password for your local `MySQL` instance. 188 | 189 | Prevent this from happening by appending your `wp valet` commands like such: `wp valet new site --dbpass=local_root_password`. 190 | 191 | At this point, you can: 192 | 1) Either create a `wp-config.php` file manually, 193 | 2) use `wp config`command to have wp-cli create one for you, or 194 | 3) use `wp valet destroy site` and try running your `wp valet new` command again, this time using the `--dbpass` attribute. 195 | 196 | ### Configuring Alternate Defaults 197 | As with other `wp-cli` commands, you can set default attributes when running `wp valet`. 198 | 199 | Simply add the appropriate details in `~/.wp-cli/config.yml`: 200 | 201 | ```yml 202 | valet new: 203 | ## Uncomment or update the relevant lines when necessary to set your own defaults. 204 | project: wp # or bedrock 205 | # in: # override - defaults to current directory 206 | version: latest 207 | # locale: # use if not English 208 | db: mysql # or sqlite 209 | # dbname: # defaults to wp_name 210 | dbuser: root # or any other local user capable of creating databases (MySQL only) 211 | # dbpass: # enter the appropriate password if necessary (MySQL only) 212 | dbprefix: wp_ 213 | admin_user: admin 214 | admin_password: admin 215 | ## Boolean options can also be configured, too. 216 | # unsecure: false # set to true to override 217 | # portable: false # set to true to override 218 | ``` 219 | 220 | The `wp valet new` defaults are shown here as an example for clarity. 221 | 222 | One simple usage for the `config.yml` could look like 223 | ```yml 224 | valet new: 225 | dbuser: root # or any db creating user 226 | dbpass: password # set yours here 227 | ``` 228 | to enable `wp valet new site` to spin up a full, live, running local WordPress site in ~3 seconds without any additional parameters. 229 | 230 | ## Support 231 | 232 | GitHub issues aren't for general support questions, but there are other venues you can try: https://wp-cli.org/#support 233 | 234 | ## Contributing 235 | 236 | We appreciate you taking the initiative to contribute to this project. 237 | 238 | Contributing isn’t limited to just code. We encourage you to contribute in the way that best fits your abilities, by writing tutorials, giving a demo at your local meetup, helping other users with their support questions, or revising our documentation. 239 | 240 | For a more thorough introduction, [check out WP-CLI's guide to contributing](https://make.wordpress.org/cli/handbook/contributing/). This package follows those policy and guidelines. 241 | 242 | ### Reporting a bug 243 | 244 | Think you’ve found a bug? We’d love for you to help us get it fixed. 245 | 246 | Before you create a new issue, you should [search existing issues](https://github.com/aaemnnosttv/wp-cli-valet-command/issues?q=label%3Abug%20) to see if there’s an existing resolution to it, or if it’s already been fixed in a newer version. 247 | 248 | Once you’ve done a bit of searching and discovered there isn’t an open or fixed issue for your bug, please [create a new issue](https://github.com/aaemnnosttv/wp-cli-valet-command/issues/new). Include as much detail as you can, and clear steps to reproduce if possible. For more guidance, [review our bug report documentation](https://make.wordpress.org/cli/handbook/bug-reports/). 249 | 250 | ### Creating a pull request 251 | 252 | Want to contribute a new feature? Please first [open a new issue](https://github.com/aaemnnosttv/wp-cli-valet-command/issues/new) to discuss whether the feature is a good fit for the project. 253 | 254 | Once you've decided to commit the time to seeing your pull request through, [please follow our guidelines for creating a pull request](https://make.wordpress.org/cli/handbook/pull-requests/) to make sure it's a pleasant experience. See "[Setting up](https://make.wordpress.org/cli/handbook/pull-requests/#setting-up)" for details specific to working on this package locally. 255 | 256 | 257 | *This README.md is generated dynamically from the project's codebase using `wp scaffold package-readme` ([doc](https://github.com/wp-cli/scaffold-package-command#wp-scaffold-package-readme)). To suggest changes, please submit a pull request against the corresponding part of the codebase.* 258 | -------------------------------------------------------------------------------- /TROUBLESHOOTING.md: -------------------------------------------------------------------------------- 1 | `Error: ERROR 1045 (28000): Access denied for user 'root'@'localhost'` 2 | The installer halts at the database creation stage because it doesn't have a password for your local `MySQL` instance. 3 | 4 | Prevent this from happening by appending your `wp valet` commands like such: `wp valet new site --dbpass=local_root_password`. 5 | 6 | At this point, you can: 7 | 1) Either create a `wp-config.php` file manually, 8 | 2) use `wp config`command to have wp-cli create one for you, or 9 | 3) use `wp valet destroy site` and try running your `wp valet new` command again, this time using the `--dbpass` attribute. 10 | 11 | ### Configuring Alternate Defaults 12 | As with other `wp-cli` commands, you can set default attributes when running `wp valet`. 13 | 14 | Simply add the appropriate details in `~/.wp-cli/config.yml`: 15 | 16 | ```yml 17 | valet new: 18 | ## Uncomment or update the relevant lines when necessary to set your own defaults. 19 | project: wp # or bedrock 20 | # in: # override - defaults to current directory 21 | version: latest 22 | # locale: # use if not English 23 | db: mysql # or sqlite 24 | # dbname: # defaults to wp_name 25 | dbuser: root # or any other local user capable of creating databases (MySQL only) 26 | # dbpass: # enter the appropriate password if necessary (MySQL only) 27 | dbprefix: wp_ 28 | admin_user: admin 29 | admin_password: admin 30 | ## Boolean options can also be configured, too. 31 | # unsecure: false # set to true to override 32 | # portable: false # set to true to override 33 | ``` 34 | 35 | The `wp valet new` defaults are shown here as an example for clarity. 36 | 37 | One simple usage for the `config.yml` could look like 38 | ```yml 39 | valet new: 40 | dbuser: root # or any db creating user 41 | dbpass: password # set yours here 42 | ``` 43 | to enable `wp valet new site` to spin up a full, live, running local WordPress site in ~3 seconds without any additional parameters. -------------------------------------------------------------------------------- /behat.yml: -------------------------------------------------------------------------------- 1 | default: 2 | suites: 3 | default: 4 | contexts: 5 | - WP_CLI_Valet\Tests\Context\FeatureContext 6 | paths: 7 | - features 8 | -------------------------------------------------------------------------------- /bin/install-package-tests.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -ex 4 | 5 | PACKAGE_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )"/../ && pwd )" 6 | 7 | download() { 8 | if [ `which curl` ]; then 9 | curl -s "$1" > "$2"; 10 | elif [ `which wget` ]; then 11 | wget -nv -O "$2" "$1" 12 | fi 13 | } 14 | 15 | install_wp_cli() { 16 | 17 | # the Behat test suite will pick up the executable found in $WP_CLI_BIN_DIR 18 | mkdir -p $WP_CLI_BIN_DIR 19 | download https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli-nightly.phar $WP_CLI_BIN_DIR/wp 20 | chmod +x $WP_CLI_BIN_DIR/wp 21 | 22 | } 23 | 24 | download_behat() { 25 | 26 | cd $PACKAGE_DIR 27 | download https://getcomposer.org/installer installer 28 | php installer 29 | php composer.phar require --dev behat/behat='~2.5' 30 | 31 | } 32 | 33 | install_db() { 34 | mysql -e 'CREATE DATABASE IF NOT EXISTS wp_cli_test;' -uroot 35 | mysql -e 'GRANT ALL PRIVILEGES ON wp_cli_test.* TO "wp_cli_test"@"localhost" IDENTIFIED BY "password1"' -uroot 36 | } 37 | 38 | install_wp_cli 39 | download_behat 40 | install_db 41 | -------------------------------------------------------------------------------- /command.php: -------------------------------------------------------------------------------- 1 | =5.5", 33 | "illuminate/container": ">=5.1 <5.7 || ^6.5.1 || ^7 || ^8", 34 | "symfony/filesystem": "^2.7 || ^3 || ^4", 35 | "wp-cli/config-command": "^1 || ^2", 36 | "wp-cli/core-command": "^1 || ^2", 37 | "wp-cli/db-command": "^1 || ^2", 38 | "wp-cli/wp-cli": "^1 || ^2" 39 | }, 40 | "require-dev": { 41 | "aaemnnosttv/wp-sqlite-db": "^1.0", 42 | "koodimonni/composer-dropin-installer": "^1.2", 43 | "roave/security-advisories": "dev-master", 44 | "roots/bedrock": "^1.8", 45 | "wp-cli/entity-command": "^1 || ^2", 46 | "wp-cli/eval-command": "^1 || ^2", 47 | "wp-cli/scaffold-package-command": "^2.0", 48 | "wp-cli/wp-cli-tests": "^3.0.15" 49 | }, 50 | "scripts": { 51 | "behat": "behat", 52 | "update-readme": "wp scaffold package-readme . --force" 53 | }, 54 | "extra": { 55 | "commands": [ 56 | "valet new", 57 | "valet destroy" 58 | ], 59 | "readme": { 60 | "shields": [ 61 | "[![Packagist](https://img.shields.io/packagist/v/aaemnnosttv/wp-cli-valet-command.svg)](https://packagist.org/packages/aaemnnosttv/wp-cli-valet-command)" 62 | ], 63 | "sections": [ 64 | "Using", 65 | "Installing", 66 | "Troubleshooting", 67 | "Support", 68 | "Contributing" 69 | ], 70 | "installing": { 71 | "pre": "PREREQUISITES.md" 72 | }, 73 | "troubleshooting": { 74 | "pre": "TROUBLESHOOTING.md" 75 | } 76 | } 77 | }, 78 | "config": { 79 | "sort-packages": true, 80 | "allow-plugins": { 81 | "composer/installers": true, 82 | "roots/wordpress-core-installer": true, 83 | "dealerdirect/phpcodesniffer-composer-installer": true, 84 | "koodimonni/composer-dropin-installer": true 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /features/valet-destroy.feature: -------------------------------------------------------------------------------- 1 | Feature: It can completely erase an installation. 2 | 3 | Scenario: It erases a regular WordPress install w/ MySql and https. 4 | Given a random string as {INSTALL} 5 | And a WP install in '{INSTALL}' 6 | And a session file: 7 | """ 8 | n 9 | """ 10 | 11 | Then the {INSTALL}/wp-config.php file should exist 12 | And the wp_cli_test database should exist 13 | 14 | When I try `wp valet destroy {INSTALL} < session` 15 | Then STDOUT should contain: 16 | """ 17 | This will delete all files and drop the database for the install. Are you sure? 18 | """ 19 | Then the {INSTALL} directory should exist 20 | 21 | When I run `wp valet destroy {INSTALL} --yes` 22 | Then STDOUT should contain: 23 | """ 24 | Success: {INSTALL} was destroyed. 25 | """ 26 | And the {INSTALL} directory should not exist 27 | And the wp_cli_test database should not exist 28 | -------------------------------------------------------------------------------- /features/valet-new-project-bedrock.feature: -------------------------------------------------------------------------------- 1 | Feature: It can create new installs for Valet-supported WordPress projects. 2 | 3 | @issue-62 4 | Scenario: Create a new Bedrock install. 5 | Given an empty directory 6 | And a random project name as {PROJECT} 7 | When I run `wp valet new {PROJECT} --project=bedrock` 8 | Then the {PROJECT}/web/wp-config.php file should exist 9 | And the {PROJECT}/.env file should exist 10 | And STDOUT should contain: 11 | """ 12 | Success: {PROJECT} ready! https://{PROJECT}.dev 13 | """ 14 | 15 | When I run `cd {PROJECT} && wp user list --fields=ID,user_login,user_email` 16 | Then STDOUT should be a table containing rows: 17 | | ID | user_login | user_email | 18 | | 1 | admin | admin@{PROJECT}.dev | 19 | 20 | @db:sqlite 21 | Scenario: It can create a new Bedrock install using sqlite instead of MySql. 22 | Given an empty directory 23 | And a random project name as {PROJECT} 24 | When I run `wp valet new {PROJECT} --project=bedrock --db=sqlite` 25 | And STDOUT should contain: 26 | """ 27 | Success: {PROJECT} ready! https://{PROJECT}.dev 28 | """ 29 | 30 | When I run `cd {PROJECT} && wp user list --fields=ID,user_login,user_email` 31 | Then STDOUT should be a table containing rows: 32 | | ID | user_login | user_email | 33 | | 1 | admin | admin@{PROJECT}.dev | 34 | 35 | When I try `wp valet destroy {PROJECT} --yes` 36 | Then the {PROJECT} directory should not exist 37 | 38 | @issue-10 39 | Scenario: It can create a new Bedrock install using the given path to the parent dir. 40 | Given an empty directory 41 | And a random project name as {PROJECT} 42 | And a random string as {PATH} 43 | 44 | When I run `wp valet new {PROJECT} --project=bedrock --in={PATH}` 45 | Then the {PATH}/{PROJECT}/.env file should exist 46 | 47 | @issue-32 48 | Scenario: The --dbprefix option is respected. 49 | Given an empty directory 50 | And a random project name as {PROJECT} 51 | And a random string as {PATH} 52 | 53 | When I run `wp valet new {PROJECT} --project=bedrock --in={PATH} --dbprefix=foo` 54 | Then the {PATH}/{PROJECT}/.env file should contain: 55 | """ 56 | DB_PREFIX='foo' 57 | """ 58 | And I run `wp eval 'echo $_SERVER["DB_PREFIX"];' --path={PATH}/{PROJECT}/web/wp/` 59 | Then STDOUT should be: 60 | """ 61 | foo 62 | """ 63 | 64 | @issue-73 65 | Scenario: It supports using an alternate DB host via the dbhost flag. 66 | Given an empty directory 67 | And a random project name as {PROJECT} 68 | When I run `wp valet new {PROJECT} --project=bedrock --dbhost=127.0.0.1` 69 | Then the {PROJECT}/.env file should contain: 70 | """ 71 | DB_HOST='127.0.0.1' 72 | """ 73 | And I run `wp eval 'echo $_SERVER["DB_HOST"];' --path={PROJECT}/web/wp/` 74 | Then STDOUT should be: 75 | """ 76 | 127.0.0.1 77 | """ 78 | -------------------------------------------------------------------------------- /features/valet-new.feature: -------------------------------------------------------------------------------- 1 | Feature: Create a new install. 2 | 3 | Scenario: It can create a new vanilla WordPress install, secure with https. 4 | Given an empty directory 5 | And a random project name as {PROJECT} 6 | When I run `wp valet new {PROJECT}` 7 | Then the {PROJECT}/wp-config.php file should exist 8 | And STDOUT should contain: 9 | """ 10 | Success: {PROJECT} ready! https://{PROJECT}.dev 11 | """ 12 | 13 | Scenario: It can create a new vanilla WordPress install using regular http. 14 | Given an empty directory 15 | And a random project name as {PROJECT} 16 | When I run `wp valet new {PROJECT} --unsecure` 17 | Then the {PROJECT}/wp-config.php file should exist 18 | And STDOUT should contain: 19 | """ 20 | Success: {PROJECT} ready! http://{PROJECT}.dev 21 | """ 22 | 23 | @issue-10 24 | Scenario: It accepts options for configuring the new install. 25 | Given an empty directory 26 | And a random project name as {PROJECT} 27 | And a random string as {ADMIN} 28 | And a random string as {PATH} 29 | 30 | When I run `wp valet new {PROJECT} --in={PATH} --admin_user={ADMIN} --admin_email=hello@{PROJECT}.dev --version=4.5 --dbname=wp_cli_test --dbprefix={ADMIN}_ --dbuser=wp_cli_test --dbpass=password1` 31 | Then the {PATH}/{PROJECT}/wp-config.php file should exist 32 | Then the wp_cli_test database should exist 33 | 34 | When I run `wp db tables --path={PATH}/{PROJECT}` 35 | Then STDOUT should contain: 36 | """ 37 | {ADMIN}_users 38 | """ 39 | 40 | When I run `wp core version --path={PATH}/{PROJECT}` 41 | Then STDOUT should be: 42 | """ 43 | 4.5 44 | """ 45 | 46 | When I run `wp user list --fields=ID,user_login,user_email --path={PATH}/{PROJECT}` 47 | Then STDOUT should be a table containing rows: 48 | | ID | user_login | user_email | 49 | | 1 | {ADMIN} | hello@{PROJECT}.dev | 50 | 51 | @db:sqlite 52 | Scenario: It can create a new WordPress install using sqlite for the database. 53 | Given an empty directory 54 | And a random project name as {PROJECT} 55 | When I run `wp valet new {PROJECT} --db=sqlite` 56 | Then the {PROJECT}/wp-config.php file should exist 57 | And the {PROJECT}/wp-content/db.php file should exist 58 | And the wp_{PROJECT} database should not exist 59 | And STDOUT should contain: 60 | """ 61 | Success: {PROJECT} ready! https://{PROJECT}.dev 62 | """ 63 | 64 | @db:sqlite 65 | Scenario: It can create a new portable WordPress install. 66 | Given an empty directory 67 | And a random project name as {PROJECT} 68 | When I run `wp valet new {PROJECT} --portable` 69 | Then the {PROJECT}/wp-config.php file should exist 70 | And the {PROJECT}/wp-content/db.php file should exist 71 | And the wp_{PROJECT} database should not exist 72 | And STDOUT should contain: 73 | """ 74 | Success: {PROJECT} ready! http://{PROJECT}.dev 75 | """ 76 | 77 | @issue-23 78 | Scenario: It creates new sites with a safe database name. 79 | Given an empty directory 80 | And a random project name as {PROJECT} 81 | And the wp_app_{PROJECT} database should not exist 82 | When I run `wp valet new app.{PROJECT}` 83 | Then STDOUT should contain: 84 | """ 85 | Success: app.{PROJECT} ready! https://app.{PROJECT}.dev 86 | """ 87 | And the wp_app_{PROJECT} database should exist 88 | 89 | @issue-73 90 | Scenario: It supports using an alternate DB host via the dbhost flag. 91 | Given an empty directory 92 | And a random project name as {PROJECT} 93 | When I run `wp valet new app.{PROJECT} --dbhost=127.0.0.1` 94 | Then STDOUT should contain: 95 | """ 96 | Success: app.{PROJECT} ready! https://app.{PROJECT}.dev 97 | """ 98 | And the wp_app_{PROJECT} database should exist 99 | When I run `wp config get DB_HOST --path=app.{PROJECT}` 100 | Then STDOUT should be: 101 | """ 102 | 127.0.0.1 103 | """ 104 | 105 | @db:sqlite @issue-51 106 | Scenario: skip-content is compatible with using sqlite for the DB. 107 | Given an empty directory 108 | And a wp-cli.yml file: 109 | """ 110 | core download: 111 | skip-content: true 112 | """ 113 | And a random project name as {PROJECT} 114 | When I run `wp valet new {PROJECT} --db=sqlite` 115 | Then the {PROJECT}/wp-config.php file should exist 116 | And the {PROJECT}/wp-content/db.php file should exist 117 | And the wp_{PROJECT} database should not exist 118 | And STDOUT should contain: 119 | """ 120 | Success: {PROJECT} ready! https://{PROJECT}.dev 121 | """ 122 | 123 | -------------------------------------------------------------------------------- /src/Composer.php: -------------------------------------------------------------------------------- 1 | props->projectRoot())) { 22 | mkdir($full_path, 0755, true); 23 | } 24 | 25 | Composer::createProject('roots/bedrock', $this->props->site_name, [ 26 | 'working-dir' => $this->props->parentDirectory(), 27 | 'no-install' => true, 28 | 'no-interaction' => true, 29 | 'no-dev' => true, 30 | ]); 31 | // Install dependencies with updates (required for older PHP) 32 | Composer::update([ 33 | 'working-dir' => $this->props->projectRoot(), 34 | 'no-interaction' => true, 35 | 'no-dev' => true, 36 | ]); 37 | } 38 | 39 | /** 40 | * Configure the .env 41 | */ 42 | public function configure() 43 | { 44 | Command::debug('Configuring .env'); 45 | 46 | $env_contents = str_replace( 47 | [ 48 | 'database_name', 49 | 'database_user', 50 | 'database_password', 51 | 'database_host', 52 | 'http://example.com', 53 | ], 54 | [ 55 | $this->props->databaseName(), 56 | $this->props->option('dbuser'), 57 | $this->props->databasePassword(), 58 | $this->props->option('dbhost', 'localhost'), 59 | $this->props->fullUrl(), 60 | ], 61 | file_get_contents($this->props->fullPath('.env.example')) 62 | ); 63 | // DB_PREFIX value is quoted in newer versions, not in older. 64 | $env_contents = preg_replace( 65 | '/^# DB_PREFIX=.*/m', 66 | sprintf('DB_PREFIX=\'%s\'', $this->props->option('dbprefix')), 67 | $env_contents 68 | ); 69 | $env_contents = preg_replace( 70 | '/^# DB_HOST=.*/m', 71 | sprintf('DB_HOST=\'%s\'', $this->props->option('dbhost', 'localhost')), 72 | $env_contents 73 | ); 74 | 75 | file_put_contents($this->props->fullPath('.env'), $env_contents); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/Installer/ComposerRequireSqliteIntegration.php: -------------------------------------------------------------------------------- 1 | projectRoot(); 19 | $flags = [ 20 | 'working-dir' => $workingDir, 21 | 'no-interaction' => true, 22 | ]; 23 | 24 | Composer::config( 25 | 'allow-plugins.koodimonni/composer-dropin-installer', 26 | 'true', 27 | array_merge($flags, ['no-plugins' => true]) 28 | ); 29 | 30 | Composer::_require('koodimonni/composer-dropin-installer:^1.0', $flags); 31 | 32 | Composer::config('extra.dropin-paths.web/app/', 'package:aaemnnosttv/wp-sqlite-db:src/db.php', $flags); 33 | 34 | Composer::_require('aaemnnosttv/wp-sqlite-db:^1.0', $flags); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Installer/InstallerInterface.php: -------------------------------------------------------------------------------- 1 | props = $props; 34 | } 35 | 36 | /** 37 | * Download the project files. 38 | * 39 | * @return void 40 | */ 41 | public function download() 42 | { 43 | Command::debug('Downloading WordPress'); 44 | 45 | $args = array_filter([ 46 | 'version' => $this->props->option('version'), 47 | 'locale' => $this->props->option('locale'), 48 | ]); 49 | 50 | if (! is_dir($full_path = $this->props->projectRoot())) { 51 | mkdir($full_path, 0755, true); 52 | } 53 | 54 | WP::core('download', $args); 55 | } 56 | 57 | /** 58 | * Configure the installation. 59 | * 60 | * @return void 61 | */ 62 | public function configure() 63 | { 64 | WP::config('create', [ 65 | 'dbname' => $this->props->databaseName(), 66 | 'dbuser' => $this->props->option('dbuser'), 67 | 'dbpass' => $this->props->databasePassword(), 68 | 'dbhost' => $this->props->option('dbhost'), 69 | 'dbprefix' => $this->props->option('dbprefix'), 70 | ]); 71 | } 72 | 73 | /** 74 | * Create the database for the install. 75 | */ 76 | public function createDatabase() 77 | { 78 | if ($this->props->usingSqlite()) { 79 | $this->createSqlite(); 80 | } else { 81 | $this->createMySql(); 82 | } 83 | } 84 | 85 | /** 86 | * Create a MySql database for the install. 87 | */ 88 | protected function createMySql() 89 | { 90 | Command::debug('Creating MySQL DB'); 91 | 92 | WP::db('create'); 93 | } 94 | 95 | /** 96 | * Install the sqlite plugin and database drop-in. 97 | */ 98 | public function createSqlite() 99 | { 100 | Command::debug('Installing SQLite DB drop-in'); 101 | 102 | $this->installSqliteIntegration(); 103 | } 104 | 105 | /** 106 | * Run the WordPress install. 107 | * 108 | * @return void 109 | */ 110 | public function runInstall() 111 | { 112 | Command::debug('Installing WordPress'); 113 | 114 | WP::core('install', [ 115 | 'url' => $this->props->fullUrl(), 116 | 'title' => $this->props->site_name, 117 | 'admin_user' => $this->props->option('admin_user'), 118 | 'admin_password' => $this->props->option('admin_password'), 119 | 'admin_email' => $this->props->option('admin_email', "admin@{$this->props->domain}"), 120 | 'skip-email' => true 121 | ]); 122 | } 123 | 124 | /** 125 | * Install the sqlite database drop-in. 126 | */ 127 | protected function installSqliteIntegration() 128 | { 129 | $cache = WP_CLI::get_cache(); 130 | $version = $this->getWpSqliteDbVersion(); 131 | Command::debug("Installing wp-sqlite-db: $version"); 132 | $cache_key = "aaemnnosttv/wp-sqlite-db/raw/$version/src/db.php"; 133 | 134 | if ($cache->has($cache_key)) { 135 | Command::debug("Using cached file: $cache_key"); 136 | } else { 137 | Command::debug("Downloading: https://github.com/$cache_key"); 138 | $http_request = \WP_CLI\Utils\http_request('GET', "https://github.com/$cache_key"); 139 | Command::debug($http_request->status_code, $http_request->headers->getAll()); 140 | 141 | if ($http_request->success) { 142 | $cache->write($cache_key, $http_request->body); 143 | } else { 144 | WP_CLI::error("Failed to download $cache_key"); 145 | } 146 | } 147 | 148 | // Ensure wp-content directory exists as it will not 149 | // if skip-content is passed. 150 | if (! is_dir($this->contentPath())) { 151 | mkdir($this->contentPath(), 0755, true); 152 | } 153 | 154 | $cache->export($cache_key, $this->contentPath('db.php')); 155 | } 156 | 157 | /** 158 | * Get the latest version to install. 159 | * 160 | * Attempts to fetch the latest commit hash from the GitHub API, with a fallback to 'master'. 161 | * Technically it points to the same file, but a hash is better for caching. 162 | * 163 | * @return string 164 | */ 165 | protected function getWpSqliteDbVersion() 166 | { 167 | $cache = WP_CLI::get_cache(); 168 | $cache_key = 'aaemnnosttv/wp-cli-valet-command/wp-sqlite-db/sha'; 169 | // A token isn't necessary but helps rule out rate limiting as a point of failure in CI 170 | $token = getenv('WP_CLI_VALET_GITHUB_TOKEN'); 171 | $master_branch = \WP_CLI\Utils\http_request('GET', 172 | 'https://api.github.com/repos/aaemnnosttv/wp-sqlite-db/branches/master', 173 | null, 174 | $token ? ['Authorization' => "token $token"] : [] 175 | ); 176 | 177 | // Always try to get the latest commit hash, if possible. 178 | if ($master_branch->success && ($master = json_decode($master_branch->body, true)) && isset($master['commit']['sha'])) { 179 | $cache->write($cache_key, $master['commit']['sha']); 180 | 181 | return $master['commit']['sha']; 182 | } elseif ($cache->has($cache_key)) { 183 | // If we got here, the API request failed for some reason, so use a stale version if it exists 184 | $version = $cache->read($cache_key); 185 | 186 | WP_CLI::warning("Unable to get latest wp-sqlite-db commit, falling back to $version (cached)."); 187 | 188 | return $version; 189 | } 190 | 191 | WP_CLI::warning('Unable to get latest wp-sqlite-db commit, falling back to master.'); 192 | 193 | return 'master'; 194 | } 195 | 196 | /** 197 | * Get the absolute path to the content directory, optionally combined with the given relative path. 198 | * 199 | * @param string $relative 200 | * 201 | * @return string 202 | */ 203 | protected function contentPath($relative = '') 204 | { 205 | $path = array_filter([ 206 | $this->props->fullPath(), 207 | $this->contentDir, 208 | $relative 209 | ]); 210 | 211 | return implode('/', $path); 212 | } 213 | } 214 | -------------------------------------------------------------------------------- /src/Process/FakeValet.php: -------------------------------------------------------------------------------- 1 | $arg) { 27 | if (is_array($arg)) { 28 | $assoc = array_merge($assoc, $arg); 29 | } else { 30 | $positional[] = $arg; 31 | } 32 | } 33 | 34 | return $this->run($command, $positional, $assoc); 35 | } 36 | 37 | /** 38 | * Run the process and return the result. 39 | * 40 | * @param $command 41 | * @param array $positional 42 | * @param array $assoc 43 | * 44 | * @return \WP_CLI\ProcessRun 45 | */ 46 | protected function run($command, $positional = [], $assoc = []) 47 | { 48 | $positional = \WP_CLI\Utils\args_to_str($positional); 49 | $assoc = \WP_CLI\Utils\assoc_args_to_str($assoc); 50 | $run_command = $this->rootCommand() . " $command $positional $assoc"; 51 | 52 | Command::debug(static::class, "Running: $run_command"); 53 | 54 | $result = Process::create( 55 | $run_command, 56 | $this->getCwd(), 57 | $this->getEnv() 58 | )->run(); 59 | 60 | Command::debug($result); 61 | 62 | if ($result->return_code > 0) { 63 | throw new \RuntimeException($result->stderr); 64 | } 65 | 66 | return $result; 67 | } 68 | 69 | protected function rootCommand() 70 | { 71 | return $this->command; 72 | } 73 | 74 | /** 75 | * @return null 76 | */ 77 | protected function getCwd() 78 | { 79 | return null; 80 | } 81 | 82 | /** 83 | * @return array 84 | */ 85 | protected function getEnv() 86 | { 87 | return [ 88 | 'HOME' => getenv('HOME'), 89 | 'PATH' => getenv('PATH'), 90 | ]; 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/Process/SystemComposer.php: -------------------------------------------------------------------------------- 1 | __call('create-project', func_get_args()); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/Process/SystemValet.php: -------------------------------------------------------------------------------- 1 | run('secure', [$domain]); 19 | } 20 | 21 | /** 22 | * Remove any Valet self-signed TLS certificate for this installation. 23 | * 24 | * @param string $domain 25 | * 26 | * @return mixed 27 | */ 28 | public function unsecure($domain) 29 | { 30 | return $this->run('unsecure', [$domain]); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Process/SystemWp.php: -------------------------------------------------------------------------------- 1 | props = $props; 24 | } 25 | 26 | protected function rootCommand() 27 | { 28 | return \WP_CLI::get_php_binary() . ' ' . $GLOBALS['argv'][0]; 29 | } 30 | 31 | protected function getCwd() 32 | { 33 | return $this->props->projectRoot(); 34 | } 35 | 36 | protected function getEnv() 37 | { 38 | return array_merge(parent::getEnv(), [ 39 | 'WP_CLI_PACKAGES_DIR' => getenv('WP_CLI_PACKAGES_DIR'), 40 | 'WP_CLI_CONFIG_PATH' => getenv('WP_CLI_CONFIG_PATH'), 41 | ]); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Process/ValetInterface.php: -------------------------------------------------------------------------------- 1 | positional = $positional; 28 | $this->options = $options; 29 | } 30 | 31 | /** 32 | * Populate a few computed properties. 33 | */ 34 | public function populate() 35 | { 36 | $this->site_name = preg_replace('/[^a-zA-Z0-9\.]/', '-', $this->positional[0]); 37 | $this->domain = sprintf('%s.%s', $this->site_name, Config::get('tld') ?: Config::get('domain')); 38 | } 39 | 40 | /** 41 | * @return bool 42 | */ 43 | public function usingSqlite() 44 | { 45 | return 'sqlite' == $this->option('db') 46 | || $this->option('portable'); 47 | } 48 | 49 | /** 50 | * Get the database name as specified by the user, or fallback to a sensible default. 51 | * 52 | * @return string 53 | */ 54 | public function databaseName() 55 | { 56 | return preg_replace( '/[^a-zA-Z0-9_-]/', '_', 57 | $this->option('dbname', "wp_{$this->site_name}") 58 | ); 59 | } 60 | 61 | /** 62 | * Get the database name as specified by the user, or fallback to an empty string. 63 | * 64 | * @return string 65 | */ 66 | public function databasePassword() 67 | { 68 | return $this->option('dbpass', ''); 69 | } 70 | 71 | /** 72 | * Get the absolute path to the project's root directory. 73 | * 74 | * @return string 75 | */ 76 | public function projectRoot() 77 | { 78 | return $this->fullPath(); 79 | } 80 | 81 | /** 82 | * Get the absolute path to the root install's parent directory. 83 | * 84 | * @return string 85 | */ 86 | public function parentDirectory() 87 | { 88 | return dirname($this->fullPath()); 89 | } 90 | 91 | /** 92 | * Get the absolute file path to the root directory of the install. 93 | * 94 | * @param string $relative A path relative to the project root. 95 | * 96 | * @return string 97 | */ 98 | public function fullPath($relative = '') 99 | { 100 | $parts = array_filter([ 101 | $this->option('in', getcwd()), 102 | $this->site_name, 103 | $relative 104 | ]); 105 | 106 | return implode('/', $parts); 107 | } 108 | 109 | /** 110 | * Get the full URL to the install. 111 | * 112 | * @return string 113 | */ 114 | public function fullUrl() 115 | { 116 | return sprintf('%s://%s', 117 | $this->isSecure() ? 'https' : 'http', 118 | $this->domain 119 | ); 120 | } 121 | 122 | /** 123 | * Whether or not the install will be secured with https. 124 | * 125 | * @return bool 126 | */ 127 | public function isSecure() 128 | { 129 | return ! ($this->option('unsecure') || $this->option('portable')); 130 | } 131 | 132 | /** 133 | * Get an option value by name. 134 | * 135 | * @param $name 136 | * @param null $default 137 | * 138 | * @return mixed|null 139 | */ 140 | public function option($name, $default = null) 141 | { 142 | return isset($this->options[$name]) ? $this->options[$name] : $default; 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /src/Valet.php: -------------------------------------------------------------------------------- 1 | [static::class, 'boot'] 38 | ]); 39 | } 40 | 41 | /** 42 | * Boot the command. 43 | */ 44 | public static function boot() 45 | { 46 | static::$container = $container = new Container; 47 | 48 | $container->singleton('valet', getenv('BEHAT_RUN') ? FakeValet::class : SystemValet::class); 49 | $container->singleton('wp', SystemWp::class); 50 | $container->singleton('composer', SystemComposer::class); 51 | $container->singleton('config', function () { 52 | return getenv('BEHAT_RUN') 53 | ? new ValetConfig(['tld' => 'dev']) 54 | : ValetConfig::loadSystem(); 55 | }); 56 | 57 | $container->bind('wp-installer', WordPressInstaller::class); 58 | $container->bind('bedrock-installer', BedrockInstaller::class); 59 | } 60 | 61 | /** 62 | * Create a new WordPress install -- fast 63 | * 64 | * This command will spin up a new WordPress installation -- complete with database and https 65 | * _ready-to-use in your browser_ faster than you can put your pants on. 66 | * 67 | * **NB** If you have not used `valet park` for the directory or parent directory you are 68 | * running the installation in you need to do a `valet link` to make sure the site will run 69 | * without running into 404s. 70 | * 71 | * ## OPTIONS 72 | * 73 | * 74 | * : Site domain name without TLD. This will become the directory name of the project root. 75 | * Eg: To create an install for example.dev, `wp valet new example` 76 | * 77 | * [--project=] 78 | * : The WordPress project to install. Choose from any project supported by Laravel Valet. 79 | * --- 80 | * default: wp 81 | * options: 82 | * - wp 83 | * - bedrock 84 | * --- 85 | * 86 | * [--in=] 87 | * : Specify the path to the parent directory to create the install in. 88 | * Defaults to the current working directory. 89 | * 90 | * [--version=] 91 | * : WordPress version to install. 92 | * --- 93 | * default: latest 94 | * --- 95 | * 96 | * [--locale=] 97 | * : Select which language you want to install. 98 | * 99 | * [--db=] 100 | * : Database driver to provision the site with. 101 | * --- 102 | * default: mysql 103 | * options: 104 | * - mysql 105 | * - sqlite 106 | * --- 107 | * 108 | * [--dbname=] 109 | * : Database name (MySQL only). 110 | * Defaults to 'wp_'. 111 | * 112 | * [--dbuser=] 113 | * : Database User (MySQL only). 114 | * --- 115 | * default: root 116 | * --- 117 | * 118 | * [--dbpass=] 119 | * : Set the database user password (MySQL only). 120 | * --- 121 | * Default: '' 122 | * --- 123 | * 124 | * [--dbhost=] 125 | * : Set the database host. 126 | * --- 127 | * default: localhost 128 | * --- 129 | * 130 | * [--dbprefix=] 131 | * : Set the database table prefix. 132 | * --- 133 | * default: 'wp_' 134 | * --- 135 | * 136 | * [--admin_user=] 137 | * : The username to create for the WordPress admin user. 138 | * --- 139 | * default: admin 140 | * --- 141 | * 142 | * [--admin_password=] 143 | * : The password to create for the WordPress admin user. 144 | * --- 145 | * default: admin 146 | * --- 147 | * 148 | * [--admin_email=] 149 | * : The email to use for the WordPress admin user. 150 | * 151 | * [--unsecure] 152 | * : Provision the site for http rather than https. 153 | * 154 | * [--portable] 155 | * : Provision the site to be portable. Implies --unsecure and --db=sqlite. 156 | * 157 | * @subcommand new 158 | * 159 | * @when before_wp_load 160 | * 161 | * @param $args 162 | * @param $assoc_args 163 | */ 164 | public function _new($args, $assoc_args) 165 | { 166 | $this->setup_props($args, $assoc_args); 167 | 168 | $project = $this->props->option('project'); 169 | 170 | if (! $installer = $this->getInstaller($project)) { 171 | WP_CLI::error("No installer found for project: '$project'"); 172 | } 173 | 174 | static::debug(sprintf('Installing using %s', get_class($installer))); 175 | 176 | WP_CLI::line("Don't go anywhere, this should only take a second..."); 177 | 178 | try { 179 | $installer->download(); 180 | $installer->configure(); 181 | $installer->createDatabase(); 182 | $installer->runInstall(); 183 | } catch (\Exception $e) { 184 | $this->exceptionHandler($e); 185 | } 186 | 187 | if ($this->props->isSecure()) { 188 | Valet::secure($this->props->site_name); 189 | } 190 | 191 | WP_CLI::success($this->props->site_name . ' ready! ' . $this->props->fullUrl()); 192 | } 193 | 194 | /** 195 | * Completely remove an installation. 196 | * 197 | * This will drop the database, and delete all of the files as well as 198 | * remove any self-signed TLS certificate that was generated for serving 199 | * this install over https. 200 | * 201 | * ## OPTIONS 202 | * 203 | * 204 | * : Site domain name without TLD. It should match the directory name of the project root. 205 | * 206 | * [--yes] 207 | * : Pre-approve the confirmation to delete all files and drop the database. 208 | * 209 | * @when before_wp_load 210 | * 211 | * @param $args 212 | * @param $assoc_args 213 | */ 214 | public function destroy($args, $assoc_args) 215 | { 216 | $this->setup_props($args, $assoc_args); 217 | $project_abspath = $this->props->projectRoot(); 218 | 219 | if (! is_dir($project_abspath)) { 220 | WP_CLI::error("No install exists at $project_abspath"); 221 | } 222 | 223 | static::debug("Preparing to destroy {$this->props->site_name}."); 224 | 225 | WP_CLI::confirm('This will delete all files and drop the database for the install. Are you sure?', $assoc_args); 226 | 227 | try { 228 | static::debug('Dropping database...'); 229 | WP::db('drop', ['yes' => true]); 230 | } catch (\Exception $e) { 231 | WP_CLI::warning('The database was unable to be dropped. Disregard this warning if using sqlite for this site.'); 232 | } 233 | 234 | try { 235 | static::debug('Removing any TLS certificate for this install...'); 236 | Valet::unsecure($this->props->site_name); 237 | } catch (\Exception $e) { 238 | $this->exceptionHandler($e); 239 | } 240 | 241 | static::debug('Removing all files...'); 242 | if ($this->rm_rf($project_abspath)) { 243 | WP_CLI::success("{$this->props->site_name} was destroyed."); 244 | } else { 245 | WP_CLI::warning('Some files were unable to be deleted.'); 246 | } 247 | } 248 | 249 | /** 250 | * Recursively delete all files and directories within (and including) the given path. 251 | * 252 | * @param $abspath 253 | * 254 | * @return bool 255 | */ 256 | protected function rm_rf($abspath) 257 | { 258 | (new Filesystem())->remove($abspath); 259 | 260 | return ! file_exists($abspath); 261 | } 262 | 263 | /** 264 | * @param $project 265 | * 266 | * @return InstallerInterface 267 | */ 268 | protected function getInstaller($project) 269 | { 270 | return $this->resolve("$project-installer"); 271 | } 272 | 273 | /** 274 | * Setup properties based on command arguments. 275 | * 276 | * @param array $args positional arguments 277 | * @param array $assoc_args associative arguments 278 | */ 279 | protected function setup_props($args, $assoc_args) 280 | { 281 | $this->props = $props = new Props($args, $assoc_args); 282 | $props->populate(); 283 | $this->container()->instance(Props::class, $props); 284 | } 285 | 286 | /** 287 | * @param mixed ..$message 288 | */ 289 | public static function debug() 290 | { 291 | foreach (func_get_args() as $arg) { 292 | $message = is_scalar($arg) ? (string) $arg : print_r($arg, true); 293 | WP_CLI::debug($message, 'aaemnnosttv/wp-cli-valet-command'); 294 | } 295 | } 296 | 297 | /** 298 | * Get the IoC container instance for the command. 299 | * 300 | * @return Container 301 | */ 302 | public static function container() 303 | { 304 | return static::$container; 305 | } 306 | 307 | /** 308 | * Resolve an instance from the container. 309 | * 310 | * @param $abstract 311 | * 312 | * @return mixed 313 | */ 314 | public static function resolve($abstract) 315 | { 316 | return static::container()->make($abstract); 317 | } 318 | 319 | /** 320 | * @param $e 321 | */ 322 | protected function exceptionHandler($e) 323 | { 324 | WP_CLI::error(preg_replace('/^Error: /', '', $e->getMessage())); 325 | } 326 | } 327 | -------------------------------------------------------------------------------- /src/ValetConfig.php: -------------------------------------------------------------------------------- 1 | data = $data; 17 | } 18 | 19 | /** 20 | * @throws \Exception 21 | */ 22 | public static function loadSystem() 23 | { 24 | $config_path = file_exists("{$_SERVER['HOME']}/.config/valet/config.json") 25 | ? "{$_SERVER['HOME']}/.config/valet/config.json" 26 | : "{$_SERVER['HOME']}/.valet/config.json"; 27 | 28 | if (! file_exists($config_path)) { 29 | throw new \Exception('Valet configuration file not found.'); 30 | } 31 | 32 | return new static(json_decode(file_get_contents($config_path), true)); 33 | } 34 | 35 | public function get($key) 36 | { 37 | return isset($this->data[$key]) ? $this->data[$key] : null; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/WP.php: -------------------------------------------------------------------------------- 1 | variables[$name] = substr(uniqid('v'), 0, 8); // ensure the string starts with a letter 16 | } 17 | 18 | /** 19 | * @Given /^a random project name as \{(\w+)\}$/ 20 | */ 21 | public function aRandomProjectNameAs($name) 22 | { 23 | $this->variables[$name] = uniqid('valet-test-'); 24 | } 25 | 26 | /** 27 | * @Then /^the ([^\s]+) database should( not)? exist$/ 28 | */ 29 | public function theGivenDatabaseShouldNotExist($database_name, $should_not_exist = false) { 30 | $database_name = $this->replace_variables($database_name); 31 | 32 | $process = Process::create("mysql -e 'SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = \"$database_name\";' -uroot") 33 | ->run(); 34 | 35 | $exists = strlen(trim($process->stdout)) > 0; 36 | $should_exist = ! $should_not_exist; 37 | 38 | if ($exists && $should_not_exist) { 39 | throw new Exception("Failed to assert that no database exists with the name '$database_name'"); 40 | } elseif (! $exists && $should_exist) { 41 | throw new Exception("Failed to assert that a database exists with the name '$database_name'"); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /utils/behat-tags.php: -------------------------------------------------------------------------------- 1 | ' ) 38 | ); 39 | 40 | # Skip Github API tests by default because of rate limiting. See https://github.com/wp-cli/wp-cli/issues/1612 41 | $skip_tags[] = '@github-api'; 42 | 43 | if ( !empty( $skip_tags ) ) { 44 | echo '--tags=~' . implode( '&&~', $skip_tags ); 45 | } 46 | 47 | --------------------------------------------------------------------------------