UNPKG

@wordpress/env

Version:

A zero-config, self contained local WordPress environment for development and testing.

306 lines (262 loc) 10.8 kB
'use strict'; /** * External dependencies */ const path = require( 'path' ); const { writeFile, mkdir } = require( 'fs' ).promises; const { existsSync } = require( 'fs' ); const yaml = require( 'js-yaml' ); /** * Internal dependencies */ const { loadConfig, ValidationError } = require( './config' ); const buildDockerComposeConfig = require( './build-docker-compose-config' ); /** * @typedef {import('./config').WPConfig} WPConfig */ /** * Initializes the local environment so that Docker commands can be run. Reads * ./.wp-env.json, creates ~/.wp-env, ~/.wp-env/docker-compose.yml, and * ~/.wp-env/Dockerfile. * * @param {Object} options * @param {Object} options.spinner A CLI spinner which indicates progress. * @param {boolean} options.debug True if debug mode is enabled. * @param {string} options.xdebug The Xdebug mode to set. Defaults to "off". * @param {boolean} options.writeChanges If true, writes the parsed config to the * required docker files like docker-compose * and Dockerfile. By default, this is false * and only the `start` command writes any * changes. * @return {WPConfig} The-env config object. */ module.exports = async function initConfig( { spinner, debug, xdebug = 'off', writeChanges = false, } ) { const config = await loadConfig( path.resolve( '.' ) ); config.debug = debug; // Adding this to the config allows the start command to understand that the // config has changed when only the xdebug param has changed. This is needed // so that Docker will rebuild the image whenever the xdebug flag changes. config.xdebug = xdebug; const dockerComposeConfig = buildDockerComposeConfig( config ); if ( config.debug ) { spinner.info( `Config:\n${ JSON.stringify( config, null, 4 ) }\n\nDocker Compose Config:\n${ JSON.stringify( dockerComposeConfig, null, 4 ) }` ); spinner.start(); } /** * We avoid writing changes most of the time so that we can better pass params * to the start command. For example, say you start wp-env with Xdebug enabled. * If you then run another command, like opening bash in the wp instance, it * would turn off Xdebug in the Dockerfile because it wouldn't have the --xdebug * arg. This basically makes it such that wp-env start is the only command * which updates any of the Docker configuration. */ if ( writeChanges ) { await mkdir( config.workDirectoryPath, { recursive: true } ); await writeFile( config.dockerComposeConfigPath, yaml.dump( dockerComposeConfig ) ); // 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.' ); process.exit( 1 ); } return config; }; /** * Generates the Dockerfile used by wp-env's `wordpress` and `tests-wordpress` instances. * * @param {string} env The environment we're installing -- development or tests. * @param {WPConfig} config The configuration object. * @return {string} The dockerfile contents. */ 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. # stretch (https://lists.debian.org/debian-devel-announce/2023/03/msg00006.html) RUN touch /etc/apt/sources.list 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 -o -g $HOST_GID $HOST_USERNAME || true RUN useradd -mlo -u $HOST_UID -g $HOST_GID $HOST_USERNAME || true # Install any dependencies we need in the container. ${ installDependencies( 'wordpress', env, config ) }`; } /** * Generates the Dockerfile used by wp-env's `cli` and `tests-cli` instances. * * @param {string} env The environment we're installing -- development or tests. * @param {WPConfig} config The configuration object. * @return {string} The dockerfile contents. */ 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 # 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', env, 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" ] `; } /** * Generates content for the Dockerfile to install dependencies. * * @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( 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. switch ( service ) { case '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 # Install git RUN apt-get -qy install git # Set up sudo so they can have root access. RUN apt-get -qy install sudo RUN echo "#$HOST_UID ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers`; break; } case 'cli': { dockerFileContent += ` # Make sure we're working with the latest packages. RUN apk update # Install some basic PHP dependencies. RUN apk --no-cache add $PHPIZE_DEPS && touch /usr/local/etc/php/php.ini # Set up sudo so they can have root access. RUN apk --no-cache add sudo linux-headers RUN echo "#$HOST_UID ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers`; break; } default: { throw new Error( `Invalid service "${ service }" given` ); } } dockerFileContent += getXdebugConfig( config.xdebug, config.env[ env ].phpVersion ); // 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 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;" 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_UID:$HOST_GID ENV PATH="\${PATH}:/home/$HOST_USERNAME/.composer/vendor/bin" RUN composer global require --dev phpunit/phpunit:"^5.7.21 || ^6.0 || ^7.0 || ^8.0 || ^9.0 || ^10.0" USER root`; return dockerFileContent; } /** * Gets the Xdebug config based on the options in the config object. * * @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 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 ValidationError( 'Something went wrong when parsing the 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 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 ` 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`; }