diff --git a/packages/env/CHANGELOG.md b/packages/env/CHANGELOG.md index 9aecc4d7a9b97b..f7769b044d9337 100644 --- a/packages/env/CHANGELOG.md +++ b/packages/env/CHANGELOG.md @@ -4,8 +4,7 @@ ### Breaking Change -- Docker containers now run as the host user. This should resolve problems with permissions arising from different owners -between the host, web container, and cli container. If you still encounter permissions issues, try running `npx wp-env destroy` so that the environment can be recreated with the correct permissions. +- Docker containers now run as the host user. This should resolve problems with permissions arising from different owners between the host, web container, and cli container. If you still encounter permissions issues, try running `npx wp-env destroy` so that the environment can be recreated with the correct permissions. - Remove the `composer` and `phpunit` Docker containers. If you are currently using the `run composer` or `run phpunit` command you can migrate to `run cli composer` or `run tests-cli phpunit` respectively. Note that with `composer`, you will need to use the `--env-cwd` option to navigate to your plugin's directory as it is no longer the default working directory. ### New feature @@ -19,10 +18,12 @@ between the host, web container, and cli container. If you still encounter permi ### Bug fix - Ensure `wordpress`, `tests-wordpress`, `cli`, and `tests-cli` always build the correct Docker image. +- Fix Xdebug while using PHP 7.2x. ### Enhancement - `wp-env run ...` now uses docker-compose exec instead of docker-compose run. As a result, it is much faster, since commands are executed against existing services, rather than creating them from scratch each time. +- Increase the maximum upload size to 1GB. ## 6.0.0 (2023-04-26) diff --git a/packages/env/README.md b/packages/env/README.md index 3c0da339e879c9..3493c08bf6495b 100644 --- a/packages/env/README.md +++ b/packages/env/README.md @@ -183,6 +183,26 @@ Out of the box `wp-env` includes the [WordPress' PHPUnit test files](https://dev While we do provide a default `wp-tests-config.php` file within the environment, there may be cases where you want to use your own. WordPress provides a `WP_TESTS_CONFIG_FILE_PATH` constant that you can use to change the `wp-config.php` file used for testing. Set this to a desired path in your `bootstrap.php` file and the file you've chosen will be used instead of the one included in the environment. +## Using `composer`, `phpunit`, and `wp-cli` tools. + +For ease of use, Composer, PHPUnit, and wp-cli are available for in the environment. To run these executables, use `wp-env run `. For example, `wp-env run cli composer install`, or `wp-env run tests-cli phpunit`. You can also access various shells like `wp-env run cli bash` or `wp-env run cli wp shell`. + +For the `env` part, `cli` and `wordpress` share a database and mapped volumes, but more tools are available in the cli environment. You should use the `tests-cli` / `tests-wordpress` environments for a separate testing database. + +By default, the cwd of the run command is the root of the WordPress install. If you're working on a plugin, you likely need to pass `--env-cwd` to make sure composer/phpunit commands are executed relative to the plugin you're working on. For example, `wp-env run cli --env-cwd=wp-content/plugins/gutenberg composer install`. + +To make this easier, it's often helpful to add scripts in your `package.json` file: + +```json +{ + "scripts": { + "composer": "wp-env run cli --env-cwd=wp-content/plugins/gutenberg composer" + } +} +``` + +Then, `npm run composer install` would run composer install in the environment. You could also do this for phpunit, wp-cli, etc. + ## Using Xdebug Xdebug is installed in the wp-env environment, but it is turned off by default. To enable Xdebug, you can use the `--xdebug` flag with the `wp-env start` command. Here is a reference to how the flag works: @@ -310,9 +330,10 @@ back to using quotation marks; wp-env considers everything inside t quotation marks to be command argument. For example, to ask WP-CLI for its help text: +
sh
 wp-env run cli "wp --help"
- + Without the quotation marks, wp-env will print its own help text instead of passing it to the container. If you experience any problems where the command is not being passed correctly, fall back to using quotation marks. @@ -396,6 +417,7 @@ Success: Installed 1 of 1 plugins. ``` #### Changing the permalink structure + You might want to do this to enable access to the REST API (`wp-env/wp/v2/`) endpoint in your wp-env environment. The endpoint is not available with plain permalinks. **Examples** @@ -540,14 +562,14 @@ Additionally, the values referencing a URL include the specified port for the gi ## Lifecycle Hooks These hooks are executed at certain points during the lifecycle of a command's execution. Keep in mind that these will be executed on both fresh and existing -environments, so, ensure any commands you build won't break on subsequent executions. +environments, so, ensure any commands you build won't break on subsequent executions. ### After Setup Using the `afterSetup` option in `.wp-env.json` files will allow you to configure an arbitrary command to execute after the environment's setup is complete: -- `wp-env start`: Runs when the config changes, WordPress updates, or you pass the `--update` flag. -- `wp-env clean`: Runs after the selected environments have been cleaned. +- `wp-env start`: Runs when the config changes, WordPress updates, or you pass the `--update` flag. +- `wp-env clean`: Runs after the selected environments have been cleaned. You can override the `afterSetup` option using the `WP_ENV_AFTER_SETUP` environment variable. @@ -689,6 +711,29 @@ This is useful for performing some actions after setting up the environment, suc } ``` +### Advanced PHP settings + +You can set PHP settings by mapping an `.htaccess` file. This maps an `.htaccess` file to the WordPress root (`/var/www/html`) from the directory in which you run `wp-env`. + +```json +{ + "mappings": { + ".htaccess": ".htaccess" + } +} +``` + +Then, your .htaccess file can contain various settings like this: + +``` +# Note: the default upload value is 1G. +php_value post_max_size 2G +php_value upload_max_filesize 2G +php_value memory_limit 2G +``` + +This is useful if there are options you'd like to add to `php.ini`, which is difficult to access in this environment. + ## Contributing to this package This is an individual package that's part of the Gutenberg project. The project is organized as a monorepo. It's made up of multiple self-contained software packages, each with a specific purpose. The packages in this monorepo are published to [npm](https://www.npmjs.com/) and used by [WordPress](https://make.wordpress.org/core/) as well as other software projects. diff --git a/packages/env/lib/build-docker-compose-config.js b/packages/env/lib/build-docker-compose-config.js index 86c56068d99730..5336f8690cca85 100644 --- a/packages/env/lib/build-docker-compose-config.js +++ b/packages/env/lib/build-docker-compose-config.js @@ -209,6 +209,7 @@ module.exports = function buildDockerComposeConfig( config ) { WP_TESTS_DIR: '/wordpress-phpunit', }, volumes: developmentMounts, + extra_hosts: [ 'host.docker.internal:host-gateway' ], }, 'tests-wordpress': { depends_on: [ 'tests-mysql' ], @@ -226,6 +227,7 @@ module.exports = function buildDockerComposeConfig( config ) { WP_TESTS_DIR: '/wordpress-phpunit', }, volumes: testsMounts, + extra_hosts: [ 'host.docker.internal:host-gateway' ], }, cli: { depends_on: [ 'wordpress' ], @@ -241,6 +243,7 @@ module.exports = function buildDockerComposeConfig( config ) { ...dbEnv.development, WP_TESTS_DIR: '/wordpress-phpunit', }, + extra_hosts: [ 'host.docker.internal:host-gateway' ], }, 'tests-cli': { depends_on: [ 'tests-wordpress' ], @@ -256,6 +259,7 @@ module.exports = function buildDockerComposeConfig( config ) { ...dbEnv.tests, WP_TESTS_DIR: '/wordpress-phpunit', }, + extra_hosts: [ 'host.docker.internal:host-gateway' ], }, }, volumes: { diff --git a/packages/env/lib/init-config.js b/packages/env/lib/init-config.js index 6983cfafdba28d..4a573c97aae7e9 100644 --- a/packages/env/lib/init-config.js +++ b/packages/env/lib/init-config.js @@ -6,12 +6,11 @@ const path = require( 'path' ); const { writeFile, mkdir } = require( 'fs' ).promises; const { existsSync } = require( 'fs' ); const yaml = require( 'js-yaml' ); -const os = require( 'os' ); /** * Internal dependencies */ -const { loadConfig } = require( './config' ); +const { loadConfig, ValidationError } = require( './config' ); const buildDockerComposeConfig = require( './build-docker-compose-config' ); /** @@ -81,38 +80,23 @@ module.exports = async function initConfig( { yaml.dump( dockerComposeConfig ) ); - await writeFile( - path.resolve( config.workDirectoryPath, 'WordPress.Dockerfile' ), - wordpressDockerFileContents( - getBaseDockerImage( config.env.development.phpVersion, false ), - config - ) - ); - await writeFile( - path.resolve( - config.workDirectoryPath, - 'Tests-WordPress.Dockerfile' - ), - wordpressDockerFileContents( - getBaseDockerImage( config.env.tests.phpVersion, false ), - config - ) - ); - - await writeFile( - path.resolve( config.workDirectoryPath, 'CLI.Dockerfile' ), - cliDockerFileContents( - getBaseDockerImage( config.env.development.phpVersion, true ), - config - ) - ); - await writeFile( - path.resolve( config.workDirectoryPath, 'Tests-CLI.Dockerfile' ), - cliDockerFileContents( - getBaseDockerImage( config.env.tests.phpVersion, true ), - config - ) - ); + // Write four Dockerfiles for each service we provided. + // (WordPress and CLI services, then a development and test environment for each.) + for ( const imageType of [ 'WordPress', 'CLI' ] ) { + for ( const envType of [ 'development', 'tests' ] ) { + await writeFile( + path.resolve( + config.workDirectoryPath, + `${ + envType === 'tests' ? 'Tests-' : '' + }${ imageType }.Dockerfile` + ), + imageType === 'WordPress' + ? wordpressDockerFileContents( envType, config ) + : cliDockerFileContents( envType, config ) + ); + } + } } else if ( ! existsSync( config.workDirectoryPath ) ) { spinner.fail( 'wp-env has not yet been initialized. Please run `wp-env start` to install the WordPress instance before using any other commands. This is only necessary to set up the environment for the first time; it is typically not necessary for the instance to be running after that in order to use other commands.' @@ -126,13 +110,16 @@ module.exports = async function initConfig( { /** * Generates the Dockerfile used by wp-env's `wordpress` and `tests-wordpress` instances. * - * @param {string} image The base docker image to use. + * @param {string} env The environment we're installing -- development or tests. * @param {WPConfig} config The configuration object. - * * @return {string} The dockerfile contents. */ -function wordpressDockerFileContents( image, config ) { - return `FROM ${ image } +function wordpressDockerFileContents( env, config ) { + const phpVersion = config.env[ env ].phpVersion + ? ':php' + config.env[ env ].phpVersion + : ''; + + return `FROM wordpress${ phpVersion } # Update apt sources for archived versions of Debian. @@ -150,19 +137,22 @@ RUN groupadd -g $HOST_GID $HOST_USERNAME || true RUN useradd -m -u $HOST_UID -g $HOST_GID $HOST_USERNAME || true # Install any dependencies we need in the container. -${ installDependencies( 'wordpress', config ) }`; +${ installDependencies( 'wordpress', env, config ) }`; } /** * Generates the Dockerfile used by wp-env's `cli` and `tests-cli` instances. * - * @param {string} image The base docker image to use. + * @param {string} env The environment we're installing -- development or tests. * @param {WPConfig} config The configuration object. - * * @return {string} The dockerfile contents. */ -function cliDockerFileContents( image, config ) { - return `FROM ${ image } +function cliDockerFileContents( env, config ) { + const phpVersion = config.env[ env ].phpVersion + ? '-php' + config.env[ env ].phpVersion + : ''; + + return `FROM wordpress:cli${ phpVersion } # Switch to root so we can create users. USER root @@ -176,7 +166,7 @@ RUN addgroup -g $HOST_GID $HOST_USERNAME || true RUN adduser -h /home/$HOST_USERNAME -G $( getent group $HOST_GID | cut -d: -f1 ) -u $HOST_UID $HOST_USERNAME || true # Install any dependencies we need in the container. -${ installDependencies( 'cli', config ) } +${ installDependencies( 'cli', env, config ) } # Switch back to the original user now that we're done. USER www-data @@ -186,46 +176,24 @@ CMD [ "/bin/sh", "-c", "while true; do sleep 2073600; done" ] `; } -/** - * Gets the base docker image to use based on our input. - * - * @param {string} phpVersion The version of PHP to get an image for. - * @param {boolean} isCLI Indicates whether or not the image is for a CLI. - * @return {string} The Docker image to use. - */ -function getBaseDockerImage( phpVersion, isCLI ) { - // We can rely on a consistent format for PHP versions. - if ( phpVersion ) { - phpVersion = ( isCLI ? '-' : ':' ) + 'php' + phpVersion; - } else { - phpVersion = ''; - } - - let wordpressImage = 'wordpress'; - if ( isCLI ) { - wordpressImage += ':cli'; - } - - return wordpressImage + phpVersion; -} - /** * Generates content for the Dockerfile to install dependencies. * - * @param {string} environment The kind of environment that we're installing dependencies on ('wordpress' or 'cli'). - * @param {WPConfig} config The configuration object. - * + * @param {string} service The kind of service that we're installing dependencies on ('wordpress' or 'cli'). + * @param {string} env The environment we're installing dependencies for ('development' or 'tests'). + * @param {WPConfig} config The configuration object. * @return {string} The Dockerfile content for installing dependencies. */ -function installDependencies( environment, config ) { +function installDependencies( service, env, config ) { let dockerFileContent = ''; // At times we may need to evaluate the environment. This is because the // WordPress image uses Ubuntu while the CLI image uses Alpine. // Start with some environment-specific dependency installations. - if ( environment === 'wordpress' ) { - dockerFileContent += ` + switch ( service ) { + case 'wordpress': { + dockerFileContent += ` # Make sure we're working with the latest packages. RUN apt-get -qy update @@ -235,33 +203,32 @@ RUN apt-get -qy install $PHPIZE_DEPS && touch /usr/local/etc/php/php.ini # Set up sudo so they can have root access. RUN apt-get -qy install sudo RUN echo "$HOST_USERNAME ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers`; - } else { - dockerFileContent += ` + break; + } + case 'cli': { + dockerFileContent += ` RUN apk update RUN apk --no-cache add $PHPIZE_DEPS && touch /usr/local/etc/php/php.ini -RUN apk --no-cache add sudo +RUN apk --no-cache add sudo linux-headers RUN echo "$HOST_USERNAME ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers`; + break; + } + default: { + throw new Error( `Invalid service "${ service }" given` ); + } } - if ( config.xdebug !== 'off' ) { - const usingCompatiblePhp = checkXdebugPhpCompatibility( config ); - if ( usingCompatiblePhp ) { - // Discover client host does not appear to work on macOS with Docker. - const clientDetectSettings = - os.type() === 'Linux' - ? 'xdebug.discover_client_host=true' - : 'xdebug.client_host="host.docker.internal"'; + dockerFileContent += getXdebugConfig( + config.xdebug, + config.env[ env ].phpVersion + ); - dockerFileContent += ` -RUN if [ -z "$(pecl list | grep xdebug)" ] ; then pecl install xdebug ; fi -RUN docker-php-ext-enable xdebug -RUN echo 'xdebug.start_with_request=yes' >> /usr/local/etc/php/php.ini -RUN echo 'xdebug.mode=true' >> /usr/local/etc/php/php.ini -RUN echo '${ clientDetectSettings }' >> /usr/local/etc/php/php.ini`; - } - } + // Add better PHP settings. + dockerFileContent += ` +RUN echo 'upload_max_filesize = 1G' >> /usr/local/etc/php/php.ini +RUN echo 'post_max_size = 1G' >> /usr/local/etc/php/php.ini`; - // Make sure Composer is available for use in the container. + // Make sure Composer is available for use in all services. dockerFileContent += ` RUN curl -sS https://getcomposer.org/installer -o /tmp/composer-setup.php RUN export COMPOSER_HASH=\`curl -sS https://composer.github.io/installer.sig\` && php -r "if (hash_file('SHA384', '/tmp/composer-setup.php') === '$COMPOSER_HASH') { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('/tmp/composer-setup.php'); } echo PHP_EOL;" @@ -280,36 +247,50 @@ USER root`; } /** - * Checks the configured PHP version - * against the minimum version supported by Xdebug + * Gets the Xdebug config based on the options in the config object. * - * @param {WPConfig} config - * @return {boolean} Whether the PHP version is supported by Xdebug + * @param {string} xdebugMode The Xdebug mode set in the config. + * @param {string} phpVersion The php version set in the environment. + * @return {string} The Xdebug config -- can be an empty string when it's not used. */ -function checkXdebugPhpCompatibility( config ) { - // By default, an undefined phpVersion uses the version on the docker image, - // which is supported by Xdebug 3. - const phpCompatibility = true; - - // If PHP version is defined - // ensure it meets the Xdebug minimum compatibility requirment. - if ( config.env.development.phpVersion ) { - const versionTokens = config.env.development.phpVersion.split( '.' ); +function getXdebugConfig( xdebugMode = 'off', phpVersion ) { + if ( xdebugMode === 'off' ) { + return ''; + } + + let xdebugVersion = 'xdebug'; + + if ( phpVersion ) { + const versionTokens = phpVersion.split( '.' ); const majorVer = parseInt( versionTokens[ 0 ] ); const minorVer = parseInt( versionTokens[ 1 ] ); if ( isNaN( majorVer ) || isNaN( minorVer ) ) { - throw new Error( + throw new ValidationError( 'Something went wrong when parsing the PHP version.' ); } - // Xdebug 3 supports 7.2 and higher - // Ensure user has specified a compatible PHP version. + // Throw an error if someone tries to use Xdebug with an unsupported PHP version. + // Xdebug 3 only supports 7.2 and higher. if ( majorVer < 7 || ( majorVer === 7 && minorVer < 2 ) ) { - throw new Error( 'Cannot use XDebug 3 on PHP < 7.2.' ); + throw new ValidationError( + `Cannot use XDebug 3 with PHP < 7.2. Your PHP version is ${ phpVersion }.` + ); + } + + // For now, we support PHP 7 by installing the final version of Xdebug to + // support PHP 7 when the environment uses that version. By default, use the + // latest version. + if ( majorVer === 7 ) { + xdebugVersion = 'xdebug-3.1.6'; } } - return phpCompatibility; + return ` +RUN if [ -z "$(pecl list | grep ${ xdebugVersion })" ] ; then pecl install ${ xdebugVersion } ; fi +RUN docker-php-ext-enable xdebug +RUN echo 'xdebug.start_with_request=yes' >> /usr/local/etc/php/php.ini +RUN echo 'xdebug.mode=${ xdebugMode }' >> /usr/local/etc/php/php.ini +RUN echo 'xdebug.client_host="host.docker.internal"' >> /usr/local/etc/php/php.ini`; }