diff --git a/package.json b/package.json index e0dcf82aca3bfe..029fb945b55f1d 100644 --- a/package.json +++ b/package.json @@ -272,7 +272,7 @@ "fixtures:generate": "cross-env GENERATE_MISSING_FIXTURES=y npm run test:unit test/integration/full-content/ && npm run format test/integration/fixtures/blocks/*.json", "fixtures:regenerate": "npm-run-all fixtures:clean fixtures:generate", "format": "wp-scripts format", - "format:php": "wp-env run composer run-script format", + "format:php": "wp-env run --env-cwd=\"wp-content/plugins/gutenberg\" cli composer run-script format", "lint": "concurrently \"npm run lint:lockfile\" \"npm run lint:tsconfig\" \"npm run lint:js\" \"npm run lint:pkg-json\" \"npm run lint:css\"", "lint:css": "wp-scripts lint-style \"**/*.scss\"", "lint:css:fix": "npm run lint:css -- --fix", @@ -281,8 +281,8 @@ "lint:lockfile": "node ./bin/validate-package-lock.js", "lint:tsconfig": "node ./bin/validate-tsconfig.mjs", "lint:md:docs": "wp-scripts lint-md-docs", - "prelint:php": "wp-env run composer \"update --no-interaction\"", - "lint:php": "wp-env run composer run-script lint", + "prelint:php": "wp-env run --env-cwd='wp-content/plugins/gutenberg' cli composer update --no-interaction", + "lint:php": "wp-env run --env-cwd=\"wp-content/plugins/gutenberg\" cli composer run-script lint", "lint:pkg-json": "wp-scripts lint-pkg-json . 'packages/*/package.json'", "native": "npm run --prefix packages/react-native-editor", "other:changelog": "node ./bin/plugin/cli.js changelog", @@ -318,9 +318,9 @@ "test:unit:debug": "wp-scripts --inspect-brk test-unit-js --runInBand --no-cache --verbose --config test/unit/jest.config.js ", "test:unit:profile": "wp-scripts --cpu-prof test-unit-js --runInBand --no-cache --verbose --config test/unit/jest.config.js ", "pretest:unit:php": "wp-env start", - "test:unit:php": "wp-env run tests-wordpress /var/www/html/wp-content/plugins/gutenberg/vendor/bin/phpunit -c /var/www/html/wp-content/plugins/gutenberg/phpunit.xml.dist --verbose", + "test:unit:php": "wp-env run --env-cwd=\"wp-content/plugins/gutenberg\" tests-wordpress vendor/bin/phpunit -c phpunit.xml.dist --verbose", "pretest:unit:php:multisite": "wp-env start", - "test:unit:php:multisite": "wp-env run tests-wordpress /var/www/html/wp-content/plugins/gutenberg/vendor/bin/phpunit -c /var/www/html/wp-content/plugins/gutenberg/phpunit/multisite.xml --verbose", + "test:unit:php:multisite": "wp-env run --env-cwd=\"wp-content/plugins/gutenberg\" tests-wordpress vendor/bin/phpunit -c phpunit/multisite.xml --verbose", "test:unit:update": "npm run test:unit -- --updateSnapshot", "test:unit:watch": "npm run test:unit -- --watch", "wp-env": "wp-env" diff --git a/packages/env/CHANGELOG.md b/packages/env/CHANGELOG.md index 767b382e1e096f..9aecc4d7a9b97b 100644 --- a/packages/env/CHANGELOG.md +++ b/packages/env/CHANGELOG.md @@ -6,13 +6,15 @@ - 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 -- Create an `afterSetup` option in `.wp-env.json` files for setting arbitrary commands to run after setting up WordPress when using `wp-env start` and `wp-env clean`. +- Create an `afterSetup` option in `.wp-env.json` files for setting arbitrary commands to run after setting up WordPress when using `npx wp-env start` and `npx wp-env clean`. - Add a `WP_ENV_AFTER_SETUP` environment variable to override the `afterSetup` option. -- Execute the `afterSetup` command on `wp-env start` after the environment is set up. This can happen when your config changes, WordPress updates, or you pass the `--update` flag. -- Execute the `afterSetup` command on `wp-env clean`. +- Execute the `afterSetup` command on `npx wp-env start` after the environment is set up. This can happen when your config changes, WordPress updates, or you pass the `--update` flag. +- Execute the `afterSetup` command on `npx wp-env clean`. +- Globally install `composer` and the correct version of `phpunit` in all of the Docker containers. ### Bug fix diff --git a/packages/env/lib/build-docker-compose-config.js b/packages/env/lib/build-docker-compose-config.js index 6a53166a34efa4..86c56068d99730 100644 --- a/packages/env/lib/build-docker-compose-config.js +++ b/packages/env/lib/build-docker-compose-config.js @@ -168,33 +168,6 @@ module.exports = function buildDockerComposeConfig( config ) { const developmentPorts = `\${WP_ENV_PORT:-${ config.env.development.port }}:80`; const testsPorts = `\${WP_ENV_TESTS_PORT:-${ config.env.tests.port }}:80`; - // Defaults are to use the most recent version of PHPUnit that provides - // support for the specified version of PHP. - // PHP Unit is assumed to be for Tests so use the testsPhpVersion. - let phpunitTag = 'latest'; - const phpunitPhpVersion = '-php-' + config.env.tests.phpVersion + '-fpm'; - if ( config.env.tests.phpVersion === '5.6' ) { - phpunitTag = '5' + phpunitPhpVersion; - } else if ( config.env.tests.phpVersion === '7.0' ) { - phpunitTag = '6' + phpunitPhpVersion; - } else if ( config.env.tests.phpVersion === '7.1' ) { - phpunitTag = '7' + phpunitPhpVersion; - } else if ( config.env.tests.phpVersion === '7.2' ) { - phpunitTag = '8' + phpunitPhpVersion; - } else if ( - [ '7.3', '7.4', '8.0', '8.1', '8.2' ].indexOf( - config.env.tests.phpVersion - ) >= 0 - ) { - phpunitTag = '9' + phpunitPhpVersion; - } - const phpunitImage = `wordpressdevelop/phpunit:${ phpunitTag }`; - - // If the user mounted their own uploads folder, we should not override it in the phpunit service. - const isMappingTestUploads = testsMounts.some( ( mount ) => - mount.endsWith( ':/var/www/html/wp-content/uploads' ) - ); - return { version: '3.7', services: { @@ -284,33 +257,12 @@ module.exports = function buildDockerComposeConfig( config ) { WP_TESTS_DIR: '/wordpress-phpunit', }, }, - composer: { - image: 'composer', - volumes: [ `${ config.configDirectoryPath }:/app` ], - }, - phpunit: { - image: phpunitImage, - depends_on: [ 'tests-wordpress' ], - volumes: [ - ...testsMounts, - ...( ! isMappingTestUploads - ? [ 'phpunit-uploads:/var/www/html/wp-content/uploads' ] - : [] ), - ], - environment: { - LOCAL_DIR: 'html', - WP_TESTS_DIR: '/wordpress-phpunit', - ...dbEnv.credentials, - ...dbEnv.tests, - }, - }, }, volumes: { ...( ! config.env.development.coreSource && { wordpress: {} } ), ...( ! config.env.tests.coreSource && { 'tests-wordpress': {} } ), mysql: {}, 'mysql-test': {}, - 'phpunit-uploads': {}, 'user-home': {}, 'tests-user-home': {}, }, diff --git a/packages/env/lib/commands/run.js b/packages/env/lib/commands/run.js index e949d81814b15f..c85d141cbe67eb 100644 --- a/packages/env/lib/commands/run.js +++ b/packages/env/lib/commands/run.js @@ -10,6 +10,7 @@ const path = require( 'path' ); */ const initConfig = require( '../init-config' ); const getHostUser = require( '../get-host-user' ); +const { ValidationError } = require( '../config' ); /** * @typedef {import('../config').WPConfig} WPConfig @@ -32,6 +33,8 @@ module.exports = async function run( { spinner, debug, } ) { + validateContainerExistence( container ); + const config = await initConfig( { spinner, debug } ); command = command.join( ' ' ); @@ -44,6 +47,42 @@ module.exports = async function run( { spinner.text = `Ran \`${ command }\` in '${ container }'.`; }; +/** + * Validates the container option and throws if it is invalid. + * + * @param {string} container The Docker container to run the command on. + */ +function validateContainerExistence( container ) { + // Give better errors for containers that we have removed. + if ( container === 'phpunit' ) { + throw new ValidationError( + "The 'phpunit' container has been removed. Please use 'wp-env run tests-cli --env-cwd=wp-content/path/to/plugin phpunit' instead." + ); + } + if ( container === 'composer' ) { + throw new ValidationError( + "The 'composer' container has been removed. Please use 'wp-env run cli --env-cwd=wp-content/path/to/plugin composer' instead." + ); + } + + // Provide better error output than Docker's "service does not exist" messaging. + const validContainers = [ + 'mysql', + 'tests-mysql', + 'wordpress', + 'tests-wordpress', + 'cli', + 'tests-cli', + ]; + if ( ! validContainers.includes( container ) ) { + throw new ValidationError( + `The '${ container }' container does not exist. Valid selections are: ${ validContainers.join( + ', ' + ) }` + ); + } +} + /** * Runs an arbitrary command on the given Docker container. * diff --git a/packages/env/lib/init-config.js b/packages/env/lib/init-config.js index 659278eed2160b..6983cfafdba28d 100644 --- a/packages/env/lib/init-config.js +++ b/packages/env/lib/init-config.js @@ -123,6 +123,69 @@ module.exports = async function initConfig( { return config; }; +/** + * Generates the Dockerfile used by wp-env's `wordpress` and `tests-wordpress` instances. + * + * @param {string} image The base docker image to use. + * @param {WPConfig} config The configuration object. + * + * @return {string} The dockerfile contents. + */ +function wordpressDockerFileContents( image, config ) { + return `FROM ${ image } + +# Update apt sources for archived versions of Debian. + +# stretch (https://lists.debian.org/debian-devel-announce/2023/03/msg00006.html) +RUN sed -i 's|deb.debian.org/debian stretch|archive.debian.org/debian stretch|g' /etc/apt/sources.list +RUN sed -i 's|security.debian.org/debian-security stretch|archive.debian.org/debian-security stretch|g' /etc/apt/sources.list +RUN sed -i '/stretch-updates/d' /etc/apt/sources.list + +# Create the host's user so that we can match ownership in the container. +ARG HOST_USERNAME +ARG HOST_UID +ARG HOST_GID +# When the IDs are already in use we can still safely move on. +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 ) }`; +} + +/** + * Generates the Dockerfile used by wp-env's `cli` and `tests-cli` instances. + * + * @param {string} image The base docker image to use. + * @param {WPConfig} config The configuration object. + * + * @return {string} The dockerfile contents. + */ +function cliDockerFileContents( image, config ) { + return `FROM ${ image } + +# Switch to root so we can create users. +USER root + +# Create the host's user so that we can match ownership in the container. +ARG HOST_USERNAME +ARG HOST_UID +ARG HOST_GID +# When the IDs are already in use we can still safely move on. +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 ) } + +# Switch back to the original user now that we're done. +USER www-data + +# Have the container sleep infinitely to keep it alive for us to run commands on it. +CMD [ "/bin/sh", "-c", "while true; do sleep 2073600; done" ] +`; +} + /** * Gets the base docker image to use based on our input. * @@ -146,6 +209,76 @@ function getBaseDockerImage( phpVersion, isCLI ) { 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. + * + * @return {string} The Dockerfile content for installing dependencies. + */ +function installDependencies( environment, 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 += ` +# Make sure we're working with the latest packages. +RUN apt-get -qy update + +# Install some basic PHP dependencies. +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 += ` +RUN apk update +RUN apk --no-cache add $PHPIZE_DEPS && touch /usr/local/etc/php/php.ini +RUN apk --no-cache add sudo +RUN echo "$HOST_USERNAME ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers`; + } + + 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 += ` +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`; + } + } + + // Make sure Composer is available for use in the container. + 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;" +RUN php /tmp/composer-setup.php --install-dir=/usr/local/bin --filename=composer +RUN rm /tmp/composer-setup.php`; + + // Install any Composer packages we might need globally. + // Make sure to do this as the user and ensure the binaries are available in the $PATH. + dockerFileContent += ` +USER $HOST_USERNAME +ENV PATH="\${PATH}:/home/$HOST_USERNAME/.composer/vendor/bin" +RUN composer global require --dev yoast/phpunit-polyfills:"^1.0" +USER root`; + + return dockerFileContent; +} + /** * Checks the configured PHP version * against the minimum version supported by Xdebug @@ -180,97 +313,3 @@ function checkXdebugPhpCompatibility( config ) { return phpCompatibility; } - -/** - * Generates the Dockerfile used by wp-env's `wordpress` and `tests-wordpress` instances. - * - * @param {string} image The base docker image to use. - * @param {WPConfig} config The configuration object. - * - * @return {string} The dockerfile contents. - */ -function wordpressDockerFileContents( image, config ) { - // Don't install XDebug unless it is explicitly required. - let shouldInstallXdebug = false; - - if ( config.xdebug !== 'off' ) { - const usingCompatiblePhp = checkXdebugPhpCompatibility( config ); - - if ( usingCompatiblePhp ) { - shouldInstallXdebug = true; - } - } - - return `FROM ${ image } - -# Update apt sources for archived versions of Debian. - -# stretch (https://lists.debian.org/debian-devel-announce/2023/03/msg00006.html) -RUN sed -i 's|deb.debian.org/debian stretch|archive.debian.org/debian stretch|g' /etc/apt/sources.list -RUN sed -i 's|security.debian.org/debian-security stretch|archive.debian.org/debian-security stretch|g' /etc/apt/sources.list -RUN sed -i '/stretch-updates/d' /etc/apt/sources.list - -# Prepare dependencies -RUN apt-get -qy install $PHPIZE_DEPS && touch /usr/local/etc/php/php.ini -${ shouldInstallXdebug ? installXdebug( config.xdebug ) : '' } - -# Create the host's user so that we can match ownership in the container. -ARG HOST_USERNAME -ARG HOST_UID -ARG HOST_GID -# When the IDs are already in use we can still safely move on. -RUN groupadd -g $HOST_GID $HOST_USERNAME || true -RUN useradd -m -u $HOST_UID -g $HOST_GID $HOST_USERNAME || true - -# Set up sudo so they can have root access when using 'run' commands. -RUN apt-get update -qy -RUN apt-get -qy install sudo -RUN echo "$HOST_USERNAME ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers -`; -} - -function installXdebug( enableXdebug ) { - const isLinux = os.type() === 'Linux'; - // Discover client host does not appear to work on macOS with Docker. - const clientDetectSettings = isLinux - ? 'xdebug.discover_client_host=true' - : 'xdebug.client_host="host.docker.internal"'; - - return ` -# Install Xdebug: -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=${ enableXdebug }' >> /usr/local/etc/php/php.ini -RUN echo '${ clientDetectSettings }' >> /usr/local/etc/php/php.ini - `; -} - -/** - * Generates the Dockerfile used by wp-env's `cli` and `tests-cli` instances. - * - * @param {string} image The base docker image to use. - * - * @return {string} The dockerfile contents. - */ -function cliDockerFileContents( image ) { - return `FROM ${ image } - -# Switch to root so we can create users. -USER root - -# Create the host's user so that we can match ownership in the container. -ARG HOST_USERNAME -ARG HOST_UID -ARG HOST_GID -# When the IDs are already in use we can still safely move on. -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 - -# Switch back now that we're done. -USER www-data - -# Have the container sleep infinitely to keep it alive for us to run commands on it. -CMD [ "/bin/sh", "-c", "while true; do sleep 2073600; done" ] -`; -} diff --git a/packages/env/lib/test/build-docker-compose-config.js b/packages/env/lib/test/build-docker-compose-config.js index 50f5b2e42d0fac..95cf6419d5db4d 100644 --- a/packages/env/lib/test/build-docker-compose-config.js +++ b/packages/env/lib/test/build-docker-compose-config.js @@ -99,54 +99,6 @@ describe( 'buildDockerComposeConfig', () => { ); } ); - it( 'should not map the default phpunit uploads directory if the user has specified their own directory', () => { - const envConfig = { - ...CONFIG, - mappings: { - 'wp-content/uploads': { - path: '/path/to/wp-uploads', - }, - }, - }; - const dockerConfig = buildDockerComposeConfig( { - workDirectoryPath: '/path', - env: { development: envConfig, tests: envConfig }, - } ); - const expectedVolumes = [ - 'tests-wordpress:/var/www/html', - '/path/tests-WordPress-PHPUnit/tests/phpunit:/wordpress-phpunit', - 'tests-user-home:/home/test', - '/path/to/wp-uploads:/var/www/html/wp-content/uploads', - ]; - expect( dockerConfig.services.phpunit.volumes ).toEqual( - expectedVolumes - ); - } ); - - it( 'should map the default phpunit uploads directory even if the user has specified their own directory only for the development instance', () => { - const envConfig = { - ...CONFIG, - mappings: { - 'wp-content/uploads': { - path: '/path/to/wp-uploads', - }, - }, - }; - const dockerConfig = buildDockerComposeConfig( { - workDirectoryPath: '/path', - env: { development: envConfig, tests: CONFIG }, - } ); - const expectedVolumes = [ - 'tests-wordpress:/var/www/html', - '/path/tests-WordPress-PHPUnit/tests/phpunit:/wordpress-phpunit', - 'tests-user-home:/home/test', - 'phpunit-uploads:/var/www/html/wp-content/uploads', - ]; - expect( dockerConfig.services.phpunit.volumes ).toEqual( - expectedVolumes - ); - } ); - it( 'should create "wordpress" and "tests-wordpress" volumes if they are needed by containers', () => { // CONFIG has no coreSource entry, so there are no core sources on the // local filesystem, so a volume should be created to contain core